package com.luculent.base;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.CapabilityType;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.luculent.utils.WorkId;

import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import lombok.extern.slf4j.Slf4j;
import net.lightbody.bmp.BrowserMobProxy;
import net.lightbody.bmp.BrowserMobProxyServer;
import net.lightbody.bmp.client.ClientUtil;
import net.lightbody.bmp.filters.RequestFilter;
import net.lightbody.bmp.filters.ResponseFilter;
import net.lightbody.bmp.filters.ResponseFilterAdapter;
import net.lightbody.bmp.proxy.BlacklistEntry;
import net.lightbody.bmp.proxy.CaptureType;
import net.lightbody.bmp.util.HttpMessageContents;
import net.lightbody.bmp.util.HttpMessageInfo;

@Slf4j
public class FPBAPIDriverProxy {

	public WebDriver driver = null;
	private BrowserMobProxy proxy = new BrowserMobProxyServer();
	private ChromeDriverService chromeService = null;
	private String driverPath = "F:/face/chromedriver.exe";

	public final ConcurrentHashMap<String, String> responses = new ConcurrentHashMap<String, String>();
	public final LinkedBlockingQueue<Pair<String, ImmutableMap<String, Object>>> requsets = new LinkedBlockingQueue<Pair<String, ImmutableMap<String, Object>>>();
	 
	private ProxyRequestFilter reqFilter = new ProxyRequestFilter(requsets);
	
	public ReentrantReadWriteLock requsetLock = new ReentrantReadWriteLock();

	private final ReentrantLock takeLock = new ReentrantLock();
	private final Condition notEmpty = takeLock.newCondition();

	public ProxyResponseFilter rspFilter = new ProxyResponseFilter(responses, takeLock, notEmpty);

	public boolean loginFlag = false;

	public String execute(ImmutableMap<String, Object> param, String url) {
		try {
			String requsetId = WorkId.sortUID();
			// 确保请求和参数匹配
			requsetLock.writeLock().lock();
			requsets.add(Pair.of(requsetId, param));
			driver.get(url);
			requsetLock.writeLock().unlock();

			return poll(requsetId, 30);
		} catch (Exception e) {
			requsetLock.writeLock().unlock();
		}
		return "FAIL";
	}

	public String execute(ImmutableMap<String, Object> param, By javaScriptByXpath) {
		try {
			String requsetId = WorkId.sortUID();
			requsetLock.writeLock().lock();
			requsets.add(Pair.of(requsetId, param));
			((JavascriptExecutor) driver).executeScript("arguments[0].click();", driver.findElement(javaScriptByXpath));
			requsetLock.writeLock().unlock();
			return poll(requsetId,30);
		} catch (Exception e) {
			requsetLock.writeLock().unlock();
		}
		return "FAIL";
	}

	public String execute(By javaScriptByXpath) {
		try {

			((JavascriptExecutor) driver).executeScript("arguments[0].click();", driver.findElement(javaScriptByXpath));

		} catch (Exception e) {

		}
		return "OK";
	}
	/**
     * 为了尽量减少等待时间 默认时间单位为纳秒   1秒 = 1000毫秒 = 1000000微妙 = 1000000000纳秒
     * timebacth 时间根据实际请求进行修改 线程以2的指数级进行休眠,直到timeout最接近的2的指数。
     * 例如 timebacth = 1 则线程休眠1纳秒 然后 2纳秒 4纳秒 8纳秒 直到获取数据或者达到timeout
     * 极限情况是 timebacth = 1  timeout=9223372036854775807 则线程大约会等待212天
     * 
     * **/
	/* public String poll(String key,long timebacth,long timeout) throws InterruptedException {
    	String x = null;
        long nanos = powerOfTwo(TimeUnit.NANOSECONDS.toNanos(timeout));
        long bacthNanos = powerOfTwo(TimeUnit.NANOSECONDS.toNanos(timebacth));
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
           while (!responses.containsKey(key)) {
            	if (bacthNanos > nanos){
            		log.debug("请求无响应"); 
                	return "FAIL";
                } 
            	log.debug(Thread.currentThread().getName()+":"+key+"等待时间："+(bacthNanos));
                notEmpty.awaitNanos(bacthNanos);
                bacthNanos <<= 1;
            } 
            x = responses.get(key);
            if(StringUtils.isNotBlank(x)){
            	responses.remove(key);
            }else{
            	x = "FAIL";
            } 
        } finally {
            takeLock.unlock(); 
        } 
        return x;
    }*/
	public String poll(String key,long timeout) throws InterruptedException {
		String x = null;
		long nanos = TimeUnit.SECONDS.toNanos(timeout); 
		final ReentrantLock takeLock = this.takeLock;
		takeLock.lockInterruptibly();
		try { 
			while (!responses.containsKey(key)) {
				if (nanos <= 0)
					return "FAIL";
				nanos = notEmpty.awaitNanos(nanos);
			}
			x = responses.get(key);
			if (StringUtils.isNotBlank(x)) {
				responses.remove(key);
			} else {
				x = "FAIL";
			}
		} finally {
			takeLock.unlock();
		}
		return x;
	}

	/**
	 * Returns a power of two size for the given target capacity
	 */
	private long powerOfTwo(long cap) {
		long n = cap - 1;
		n |= n >>> 1;
		n |= n >>> 2;
		n |= n >>> 4;
		n |= n >>> 8;
		n |= n >>> 16;
		n |= n >>> 32;
		return (n < 0) ? 1 : (n >= Long.MAX_VALUE) ? Long.MAX_VALUE : n + 1;
	}

	private long heartbeatTime;
	// 执行时间，时间单位为毫秒,读者可自行设定，不得小于等于0
	private static Integer cacheTime = 14400000;
	// 延迟时间，时间单位为毫秒,读者可自行设定，不得小于等于0
	private static Integer delay = 1000;

	private void heartBeat() {
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				// System.currentTimeMillis() - heartbeatTime > 1000
			}
		}, delay, cacheTime);
	}

	@PostConstruct
	public void toInit() {
		proxy.start(8181);
		Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy);
		ChromeOptions options = new ChromeOptions();
		options.setCapability(CapabilityType.PROXY, seleniumProxy);
		options.addArguments("ignore-certificate-errors");
		options.addArguments("start-maximized");
		System.setProperty("webdriver.chrome.driver", driverPath);
		options.addArguments("headless"); // 无界面参数
		options.addArguments("disable-gpu");
		options.addArguments("no-sandbox"); // 禁用沙盒 就是被这个参数搞了一天
		chromeService = new ChromeDriverService.Builder().usingDriverExecutable(new File(driverPath)).usingPort(3331)
				.build();
		try {
			chromeService.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
		driver = new ChromeDriver(chromeService, options); 
		// 隐式等待,全局起作用
		driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
		proxy.enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT);
		proxy.setBlacklist(ImmutableSet.of(new BlacklistEntry("google.com", 404),
				new BlacklistEntry("gstatic.com", 404), new BlacklistEntry("clients4.google.com", 404),
				new BlacklistEntry("beacons.gcp.gvt2.com", 404), new BlacklistEntry("play.google.com", 404),
				new BlacklistEntry("clientservices.googleapis.com", 404),
				new BlacklistEntry("chrome-devtools-frontend.appspot.com", 404),
				new BlacklistEntry("update.googleapis.com", 404)));
		proxy.addRequestFilter(reqFilter);
		proxy.addLastHttpFilterFactory(new ResponseFilterAdapter.FilterSource(rspFilter, 20971520));
		proxy.addHeader("Accept-Charset", "utf-8");
	}

	class ProxyRequestFilter implements RequestFilter {

		public ProxyRequestFilter(LinkedBlockingQueue<Pair<String, ImmutableMap<String, Object>>> requsets) {
			this.requsets = requsets;
		}

		final LinkedBlockingQueue<Pair<String, ImmutableMap<String, Object>>> requsets;

		List<String> valid = Lists.newArrayList("play.google.com", "accounts.google.com", "gstatic.com",
				"clients4.google.com", "clientservices.googleapis.com", "chrome-devtools-frontend.appspot.com",
				"update.googleapis.com", "beacons.gcp.gvt2.com");

		@Override
		public HttpResponse filterRequest(HttpRequest request, HttpMessageContents contents,
				HttpMessageInfo messageInfo) {

			// ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
			String url = request.uri();
			if (valid.contains(url)) {
				return new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.FOUND);
			}
			Pair<String, ImmutableMap<String, Object>> kv = requsets.poll();
			if (kv != null) {
				messageInfo.getOriginalRequest().headers().add("requestId", kv.getKey());
				log.debug(kv.getKey() + ":proxy发送请求：" + url);
				request.headers().remove("t");
				request.headers().remove("x");
				request.headers().add("t", kv.getValue().get("key1"));
				request.headers().add("x", kv.getValue().get("key2"));
				contents.setTextContents(String.valueOf(kv.getValue().get("key3")));
			}

			return null;
		}
	}

	class ProxyResponseFilter implements ResponseFilter {

		public ProxyResponseFilter(ConcurrentHashMap<String, String> responses, ReentrantLock takeLock,
				Condition notEmpty) {
			this.responses = responses;
			this.takeLock = takeLock;
			this.notEmpty = notEmpty;
		}

		private final ConcurrentHashMap<String, String> responses;
		private final ReentrantLock takeLock ;
		private final Condition notEmpty ;

		@Override
		public void filterResponse(HttpResponse response, HttpMessageContents contents, HttpMessageInfo messageInfo) {
			// ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
			
			String rquestId = messageInfo.getOriginalRequest().headers().get("requestId");
			if (StringUtils.isNotBlank(rquestId)) {
				if (response.status().code() == HttpResponseStatus.OK.code()){
					String reslut = contents.getTextContents();
					log.debug("响应：" + rquestId + "==" + (StringUtils.isBlank(reslut) ? "NULL" : reslut));
					responses.put(rquestId, StringUtils.isBlank(reslut) ? "NULL" : reslut);
				} else {
					log.debug("响应：" + rquestId + "==FAIL");
					responses.put(rquestId, "FAIL");
				}
				noticeGeReuslt(takeLock,notEmpty);
			} 
			
		}
	}
	
	private void noticeGeReuslt(ReentrantLock takeLock,Condition notEmpty){
		try {
			// 加锁
			takeLock.lockInterruptibly();
			notEmpty.signalAll(); 
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 释放锁
			takeLock.unlock();
		} 
	}
}
